Skip to content

feat: scale Postgres memory (config + container share) with instance RAM#100

Open
tonychang04 wants to merge 3 commits into
mainfrom
feat/scale-pg-memory-settings
Open

feat: scale Postgres memory (config + container share) with instance RAM#100
tonychang04 wants to merge 3 commits into
mainfrom
feat/scale-pg-memory-settings

Conversation

@tonychang04

@tonychang04 tonychang04 commented Jul 15, 2026

Copy link
Copy Markdown
Member

What

Right-sizes Postgres memory to the instance RAM tier, in two parts:

  1. Postgres config scalingshared_buffers, work_mem, maintenance_work_mem, effective_cache_size scale by tier (nano 32MB → 2xl 2GB shared_buffers). Previously every tier ran the nano-safe values, so larger instances left most of their RAM unused.
  2. Container-limit rebalance — Node (insforge) and PostgREST have ~fixed footprints (measured: Node <100MB, PostgREST ~3MB idle); only Postgres benefits from more RAM as the box grows. Cap those two (512MB / 256MB) and give Postgres the remainder, instead of scaling all three linearly. min() with the original linear share leaves nano/micro unchanged (load-tested config preserved).

Why the rebalance (found via load test)

Load-testing the "large" (8GB) tier showed the linear split starved Postgres to 43% of the box (3.34GB): under memory-heavy load it pinned that cap and thrashed on swap while ~4.7GB of host RAM sat idle, and the Node container held 3.3GB it never touched.

Tested (throwaway t4g.large, deployed this branch)

Large tier (8GB) Before rebalance After rebalance
Postgres container limit 3.34 GB (43%) 6.85 GB (90%)
Standard load (150 clients) 495 TPS, 0 OOM 504 TPS, 0 OOM
Sort-heavy load: PG memory pinned at 3.34 GB cap climbed to 4.1 GB, headroom to 6.85 GB, not pinned
OOM kills 0 0

Config verified correct at the large tier (shared_buffers=512MB, work_mem=8MB, max_connections=250). Nano row is the load-test-proven config from #99, unchanged.

Per-tier Postgres share after rebalance: nano/micro ~40–42% (unchanged), small 61%, medium 81%, large 90%, xl 95%, 2xl 98%.

Backward compat

Compose -c flag defaults equal the shipped postgresql.conf values, so instances without the new env vars behave exactly as before until auto-scale-memory.sh re-runs.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YC6NK3j7kYEz8e6Awu114R

Summary by CodeRabbit

  • New Features

    • Added smarter automatic memory allocation across application and database services, with defined limits and improved handling of available system memory.
    • Added automatic PostgreSQL memory tuning based on the available instance RAM.
  • Bug Fixes

    • Improved persistence of generated memory settings, ensuring outdated values are removed and the latest configuration is applied consistently.
    • Preserved existing encryption-key fallback behavior while expanding database configuration support.

postgresql.conf ships nano-safe values (shared_buffers=32MB, work_mem=2MB,
maintenance_work_mem=16MB, effective_cache_size=128MB) and every tier ran
them — a large (8G) instance got nano-tuned Postgres. Follow the #98
pattern: auto-scale-memory.sh picks per-tier values and writes them to
.env; the compose command passes them as -c flags (command line overrides
config_file). Without the env vars the -c defaults equal the conf values,
so behavior on existing hosts is unchanged until auto-scale re-runs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J9NtiFkU34HQQgRSffNb4k
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Changes

Memory scaling

Layer / File(s) Summary
Allocation and environment persistence
auto-scale-memory.sh
Service memory allocations now use caps and remainder logic; tiered PostgreSQL settings are generated, cleaned up, and persisted in .env.
Compose PostgreSQL configuration
docker-compose.yml
The Postgres command applies the generated memory settings with environment-variable defaults while retaining existing configuration and encryption fallbacks.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: jwfing, carmendou

Poem

A rabbit tuned the memory bright,
With capped-up clouds and buffers right.
Postgres found its share to keep,
While Compose carried settings deep.
“Hop!” cried Bun, “the stack’s in flight!”

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: scaling PostgreSQL memory settings and container memory share by instance RAM.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/scale-pg-memory-settings

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Checkov (3.3.8)
docker-compose.yml

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'


Comment @coderabbitai help to get the list of available commands.

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: scale Postgres memory settings with instance RAM

Summary: A tightly-scoped, low-risk follow-up to #98 that adds a RAM-tiered shared_buffers / effective_cache_size / work_mem / maintenance_work_mem block to auto-scale-memory.sh and threads the values through docker-compose.yml as -c flags with conf-equivalent defaults; the approach is sound and backward-compatible.

Requirements context: No matching spec/plan found — insforge-standalone has no docs/superpowers/ (or any docs/) directory; it is the deploy/compose repo. Assessed against the PR description, linked PRs (#98 connection scaling, #99 load evidence), and the existing code.

I verified the key backward-compat claim: the compose -c defaults (shared_buffers=32MB, effective_cache_size=128MB, work_mem=2MB, maintenance_work_mem=16MB) exactly equal the values shipped in docker-init/db/postgresql.conf:29-32, and PostgreSQL command-line -c settings override config_file, so hosts without the new .env vars behave identically to today. bash -n passes. The new sed-removal keys (auto-scale-memory.sh:119) match the appended keys, so the script stays idempotent. Tier thresholds (:90-96) align exactly with the connection-scaling thresholds (:73-79) — good consistency.

Findings

Critical

(none)

Suggestion

  • Functionality / performance — worst-case work_mem vs. the container cgroup limit (auto-scale-memory.sh:90-96). The postgres container's hard memory limit is POSTGRES_MEMORY ≈ 0.43 × usable host RAM (docker-compose.yml:12, base 150/350 × scale factor). At the top tiers the worst-case work_mem × max_connections approaches that ceiling: xl → 16MB × 400 ≈ 6.4GB + shared_buffers 1GB vs. a ~6.8GB container; 2xl → 16MB × 700 ≈ 11.2GB + shared_buffers 2GB vs. a ~13GB container. In practice the real bound is PGRST_DB_POOL (250/450, not 700) and the rarity of every backend sorting at full work_mem across multiple nodes at once — which is why this is a suggestion, not a blocker — but a cgroup OOM kills the postmaster (full DB crash), not just one backend. Consider noting in the comment that the pool cap (not max_connections) is the operative bound, and/or monitoring RSS at 2xl under load.
  • Software engineering — no assertion of the tier→values mapping. The selection logic is now two parallel 7-branch tables; an accidental future edit to one threshold could silently mis-tune a tier. The repo has no shell-test harness (so this is not a convention violation), but a small bats/shell check asserting TOTAL_MEM → (shared_buffers, work_mem, …) for representative values would guard both this block and the #98 connection block against drift.

Information

  • effective_cache_size is a planner cost hint only (no allocation), so the multi-GB values at large tiers carry no OOM risk. On a box shared with the API the chosen values (e.g. 4GB at xl, 8GB at 2xl) slightly over-estimate cache actually available to Postgres, which biases the planner toward index scans — but the values are conservative relative to host RAM, so this is fine.
  • Pre-existing (not this PR): docker-init/db/postgresql.conf:35 still reads max_connections = 60 while the compose default is :-100 and tiers reach 700. The -c override supersedes the conf value at runtime, so it's cosmetic, but the stale conf comment/value could confuse future readers — worth a cleanup pass someday.

Security: No security-relevant changes — no new user input, no secrets, no auth surface; tier values are static constants and the app.encryption_key command arg is unchanged.

Verdict

approved (informational — human approval via the separate approve flow). No blocking issues; the two suggestions are non-blocking hardening/observability notes. Note the PR is currently marked draft.

jwfing
jwfing previously approved these changes Jul 23, 2026

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - approved.

…rint services

Scaling all three container limits linearly starved Postgres to ~43% of a
large box while the Node container held GBs it never uses (Node <100MB,
PostgREST ~3MB at idle). A large-tier load test showed Postgres pinning its
3.3GB cap and thrashing under memory-heavy load with ~4.7GB host RAM idle.

Cap Node (512MB) and PostgREST (256MB) at fixed ceilings and give Postgres the
remainder. min() with the original linear share leaves nano/micro unchanged
(load-tested config preserved); large now gives Postgres ~7.4GB (90%) vs 3.3GB.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YC6NK3j7kYEz8e6Awu114R
@tonychang04 tonychang04 changed the title feat: scale Postgres memory settings with instance RAM feat: scale Postgres memory (config + container share) with instance RAM Jul 23, 2026

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: scale Postgres memory with instance RAM

Summary: Two well-scoped, load-test-backed changes to auto-scale-memory.sh + a matching docker-compose.yml command — tier-scaled Postgres memory settings and a container-limit rebalance that gives Postgres the RAM the fixed-footprint Node/PostgREST containers don't use. The logic is correct and backward-compatible; my findings are non-blocking accuracy/tuning notes.

Requirements context

No /docs/superpowers/ or docs/specs/ directory exists in this repo (it's the deploy/compose repo — root has only auto-scale-memory.sh, docker-compose.yml, docker-init/, functions). No matching spec/plan found — assessing against the PR description, the referenced load test, and the surrounding code alone. Intent is clear and internally documented in the PR body and inline comments.

I re-derived the per-tier container split independently and it matches the PR body exactly (postgres share ≈ 43/43/62/81/91/95/98% across nano→2xl), so the arithmetic checks out.


Critical

(none) — no correctness, security, data-loss, or backward-compatibility defects found.

I specifically verified backward compat: the four new -c defaults in docker-compose.yml:7 (shared_buffers=32MB, effective_cache_size=128MB, work_mem=2MB, maintenance_work_mem=16MB) exactly match the shipped docker-init/db/postgresql.conf (32MB / 128MB / 2MB / 16MB). So an instance with no new env vars behaves identically until auto-scale-memory.sh re-runs — the claim holds. The re-run path is idempotent too: the new PG_* keys are added to the sed delete list (auto-scale-memory.sh:139) before being re-appended.

Suggestion

  • Functionality / accuracy — stale rationale comment (auto-scale-memory.sh:106-109). The comment states shared_buffers "stays ~15-25% of the postgres CONTAINER limit (POSTGRES_MEMORY, ~35-40% of host RAM)". That describes the pre-rebalance linear split. After the rebalance introduced in this same PR, the Postgres container is 62–98% of usable RAM on small→2xl (only nano/micro remain ~43%), and shared_buffers is now ~7% of the container at the large tier (512MB / ~7.4GB), not 15-25%. Recommend updating the comment so future maintainers don't reason from the old ratios.
  • Performance — shared_buffers / effective_cache_size now conservative relative to the larger container (auto-scale-memory.sh:110-116). Because the rebalance hands Postgres ~90%+ of the box, shared_buffers=512MB (large) / 2GB (2xl) is a small fraction of the container, and effective_cache_size (2GB at large on an ~8GB box) under-hints the total cache available to the planner. This is safe and load-tested (504 TPS, 0 OOM), so it's non-blocking — but there may be headroom to raise both now that the memory is actually allocated to PG. Worth a follow-up load test rather than a change here.
  • Software engineering — no automated guard for the tier math (auto-scale-memory.sh). The threshold table and the min()/floor logic are easy to break silently on edit. The repo has no test harness, so this isn't a convention violation — but a lightweight --dry-run/bats check asserting the boundary values (e.g. TOTAL_MEM=7500shared_buffers=512MB, container ≈ USABLE−768) would protect the boundaries cheaply. Non-blocking given repo conventions.

Information

  • The # Verify total doesn't exceed usable memory block (auto-scale-memory.sh:120-129, pre-existing) only echoes TOTAL_ALLOCATED; it doesn't guard or clamp. With the new Postgres floor (auto-scale-memory.sh:81) plus awk rounding, TOTAL_ALLOCATED can exceed USABLE_MEM by ~1MB on nano. Cosmetic, and unchanged in spirit from before.
  • The Postgres-memory tiers and the connection-scaling/container tiers use independent TOTAL_MEM thresholds, but I checked they stay mutually consistent (e.g. shared_buffers never approaches the Postgres container limit — worst ratio is nano at ~15%), so there's no risk of a high shared_buffers on a small container.
  • Security: no security-relevant changes — no new user input reaches SQL/shell, no secrets logged (the script echoes only memory sizes), and the pre-existing app.encryption_key handling in the compose command is untouched.
  • This PR is marked draft; flagging in case it's not yet intended for merge.

Verdict

approved (informational; posted as a comment — explicit GitHub approval remains a separate human action). Zero Critical findings. The change is correct, backward-compatible, and backed by a documented load test; the notes above are accuracy and tuning follow-ups the author can take or leave.

jwfing
jwfing previously approved these changes Jul 23, 2026

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - approved.

The comment still described the pre-rebalance split (shared_buffers ~15-25%
of a container that was ~35-40% of host RAM). Post-rebalance Postgres gets
~62-98% of the box, so shared_buffers is now ~7-15% of the container and
deliberately conservative. Comment-only; no behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YC6NK3j7kYEz8e6Awu114R
@tonychang04
tonychang04 marked this pull request as ready for review July 23, 2026 02:59
@cursor

cursor Bot commented Jul 23, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: scale Postgres memory with instance RAM

Summary: A well-reasoned, load-test-backed infra tuning change — Postgres config values now scale by RAM tier and the container-limit split gives Postgres the remainder instead of scaling all three services linearly; the logic is internally consistent and backward-compatible, with no blocking issues.

Requirements context

No matching spec/plan found — insforge-standalone has no docs/superpowers/ (or docs/specs/) directory, so this was assessed against the PR description and the surrounding code alone. The change is self-contained to auto-scale-memory.sh and docker-compose.yml.

Findings

Critical

(none)

Suggestion

  • Functionality — CPU limit is not scaled alongside memory (docker-compose.yml:8-13). The Postgres container is pinned at cpus: '0.5' for every tier. The PR's premise is that "only Postgres benefits from more resources as the box grows," and on a large/xl/2xl box Postgres now gets ~90–98% of RAM but is still throttled to half a core. The sort-heavy load test measured TPS and OOM but not CPU saturation. Explicitly out of the stated (memory-only) scope, so non-blocking — but worth a follow-up: a memory-rich Postgres capped at 0.5 CPU may just move the bottleneck.
  • Software engineering — no automated coverage for the tier logic (auto-scale-memory.sh:112-119). Consistent with the repo (no test harness exists), so not a blocker. The branch table is now the third parallel tier ladder in this file (container split, connection scaling, memory settings) that must be kept in lockstep by hand. A tiny table-driven smoke test (feed a TOTAL_MEM per tier, assert the emitted .env) would cheaply guard against a future drift where, say, a shared_buffers bump outgrows its container limit.

Information

  • Backward-compat claim verified. The compose -c fallback defaults (shared_buffers=32MB, effective_cache_size=128MB, work_mem=2MB, maintenance_work_mem=16MB at docker-compose.yml:7) exactly match the shipped docker-init/db/postgresql.conf:29-32. Instances without the new env vars behave identically until auto-scale-memory.sh re-runs, as stated. ✅
  • .env regeneration is idempotent. The four new keys were correctly added to the sed delete list (auto-scale-memory.sh:141), so re-running the script won't accumulate duplicate PG_* lines. ✅
  • Per-tier sizing is safe. Spot-checked every tier: shared_buffers plus worst-case work_mem × PG_MAX_CONNECTIONS stays comfortably within the remainder-based Postgres container limit (e.g. large: 512MB + 8MB×250 ≈ 2.5GB inside a ~6.85GB container; 2xl: 2GB + 16MB×700 ≈ 13GB inside ~31GB). The POSTGRES_MEM floor at the original linear share keeps nano/micro unchanged. ✅
  • /dev/shm sizing (not introduced here, but adjacent). docker-compose.yml sets no shm_size, so the container keeps Docker's 64MB /dev/shm. shared_buffers uses anonymous mmap and is unaffected, but Postgres parallel-query dynamic shared memory (dynamic_shared_memory_type=posix) lives in /dev/shm; on the larger tiers with higher work_mem and more parallel workers this could bind. Not caused by this PR (no parallelism settings changed) — flagging only so it's on the radar for the next tuning pass.
  • Vestigial comment. # Verify total doesn't exceed usable memory (auto-scale-memory.sh:122) precedes an echo-only block with no actual verification, and since POSTGRES_MEM = USABLE_MEM − INSFORGE_MEM − POSTGREST_MEM, the total now equals usable by construction (or exceeds it by ~1MB only when the nano linear floor engages). Pre-existing, harmless.

Security

No security-relevant changes: no new user input reaches SQL/shell, no secrets logged (the echos print only tuning values), and auth/app.encryption_key handling is untouched.

Performance

This is a performance change, and a net positive — it fixes documented swap-thrashing on the large tier (Postgres 43% → 90% of the box) with load-test evidence and no OOM regressions. No hot-path or query concerns introduced.

Verdict

approved (informational — human approval via the separate approve flow). No Critical findings; the two Suggestions and the Information notes are all non-blocking follow-ups.

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - approved.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@auto-scale-memory.sh`:
- Around line 79-81: Update the PostgreSQL allocation logic around
POSTGRES_LINEAR and POSTGRES_MEM so the rounded PostgreSQL floor cannot make
INSFORGE_MEM + POSTGREST_MEM + POSTGRES_MEM exceed USABLE_MEM. Cap the
PostgreSQL allocation at the remaining usable memory while preserving the
existing linear-floor minimum whenever it fits.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fe95c9d3-178d-4347-81a8-dd09269d57cc

📥 Commits

Reviewing files that changed from the base of the PR and between 48a21b7 and 292f8a0.

📒 Files selected for processing (2)
  • auto-scale-memory.sh
  • docker-compose.yml

Comment thread auto-scale-memory.sh
Comment on lines +79 to +81
POSTGRES_LINEAR=$(awk "BEGIN {printf \"%.0f\", $POSTGRES_BASE * $SCALE_FACTOR}")
POSTGRES_MEM=$(( USABLE_MEM - INSFORGE_MEM - POSTGREST_MEM ))
if [ "$POSTGRES_MEM" -lt "$POSTGRES_LINEAR" ]; then POSTGRES_MEM=$POSTGRES_LINEAR; fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Prevent rounded allocations from exceeding usable memory.

The three linear shares are rounded independently, so the PostgreSQL floor can over-allocate. For example, with TOTAL_MEM=384MB, the result is 152 + 51 + 152 = 355MB against 354MB usable. The later summary only prints this total.

Proposed fix
-POSTGRES_LINEAR=$(awk "BEGIN {printf \"%.0f\", $POSTGRES_BASE * $SCALE_FACTOR}")
+POSTGRES_LINEAR=$(( USABLE_MEM - INSFORGE_LINEAR - POSTGREST_LINEAR ))
 POSTGRES_MEM=$(( USABLE_MEM - INSFORGE_MEM - POSTGREST_MEM ))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
POSTGRES_LINEAR=$(awk "BEGIN {printf \"%.0f\", $POSTGRES_BASE * $SCALE_FACTOR}")
POSTGRES_MEM=$(( USABLE_MEM - INSFORGE_MEM - POSTGREST_MEM ))
if [ "$POSTGRES_MEM" -lt "$POSTGRES_LINEAR" ]; then POSTGRES_MEM=$POSTGRES_LINEAR; fi
POSTGRES_LINEAR=$(( USABLE_MEM - INSFORGE_LINEAR - POSTGREST_LINEAR ))
POSTGRES_MEM=$(( USABLE_MEM - INSFORGE_MEM - POSTGREST_MEM ))
if [ "$POSTGRES_MEM" -lt "$POSTGRES_LINEAR" ]; then POSTGRES_MEM=$POSTGRES_LINEAR; fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@auto-scale-memory.sh` around lines 79 - 81, Update the PostgreSQL allocation
logic around POSTGRES_LINEAR and POSTGRES_MEM so the rounded PostgreSQL floor
cannot make INSFORGE_MEM + POSTGREST_MEM + POSTGRES_MEM exceed USABLE_MEM. Cap
the PostgreSQL allocation at the remaining usable memory while preserving the
existing linear-floor minimum whenever it fits.

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: scale Postgres memory (config + container share) with instance RAM

Summary: A well-reasoned, load-test-validated change that tiers Postgres memory settings to instance RAM and rebalances container limits so Postgres — the only service that scales with the box — gets the remainder; backward compatibility is preserved and I found no blocking issues.

Requirements context

No docs/superpowers/ (or docs/specs/) directory exists in this repo — it's a minimal deployment repo (auto-scale-memory.sh, docker-compose.yml, docker-init/, functions/), with no .github/ CI or test harness. No matching spec/plan found — assessing against the PR description, the load-test evidence in the PR body, and the referenced #99 config.

Findings

Critical

(none)

Suggestion

  • Functionality / Performance — effective_cache_size is conservative (~25% of box) at every tier (auto-scale-memory.sh:112-118). It's set to a fixed 4× shared_buffers (32→128MB, 512MB→2GB, 2GB→8GB), i.e. ~25% of total RAM per tier. After this PR's rebalance Postgres owns ~90–98% of a large/xl/2xl box, so the planner is being told far less OS cache is available than actually is. effective_cache_size is a planner cost hint, not an allocation (no OOM risk), and understating it biases the planner away from index scans toward seq scans/bitmap heap scans. Common guidance is ~50–75% of the memory available to Postgres. Unlike shared_buffers (deliberately conservative pending a follow-up load test, which I agree with), raising effective_cache_size has no downside, so it's a safe win to include or note for the follow-up. Non-blocking.

  • Functionality — worst-case work_mem accounting under-counts hash memory (auto-scale-memory.sh:110-111,112-118). The comment bounds work_mem against work_mem * max_connections, but PG15's hash_mem_multiplier defaults to 2.0, so hash-based nodes may use up to 2 × work_mem each, and a single query can have several sort/hash nodes. The realistic worst case is thus a few× higher than work_mem × max_connections. The author explicitly load-tested sort-heavy load at the large tier with 0 OOM kills and flags tuning as a documented follow-up, so this is acceptable as shipped — noting it so the follow-up load test accounts for the multiplier and multi-node queries.

Information

  • Software engineering — no automated test. Expected: this repo has no shell/compose test harness or CI (.github/ absent), and the change is validated via the documented throwaway-t4g.large load test in the PR body (before/after container limits, TPS, OOM counts). That's the appropriate validation path for this repo; not flagging as a gap.
  • Backward compatibility verified. The new compose -c defaults — shared_buffers=${PG_SHARED_BUFFERS:-32MB}, effective_cache_size=…:-128MB, work_mem=…:-2MB, maintenance_work_mem=…:-16MB (docker-compose.yml:7) — exactly match the shipped docker-init/db/postgresql.conf:29-32. Instances without the new env vars behave identically until auto-scale-memory.sh re-runs, as the PR claims. ✓
  • No startup-allocation OOM risk. Traced nano→2xl: shared_buffers stays ≤ ~17% of the computed POSTGRES_MEM container limit at every tier (tightest is micro at ~64MB/~395MB), so the upfront shared-memory allocation always fits the cgroup limit. ✓
  • .env re-run hygiene is correct. The four new PG_* keys are added to the sed delete list (auto-scale-memory.sh:141) before the block is re-appended, so repeated runs don't accumulate stale duplicates. ✓
  • Verify block is echo-only (auto-scale-memory.sh:122-131). TOTAL_ALLOCATED is computed and printed but not asserted against USABLE_MEM. By construction POSTGRES_MEM = USABLE_MEM − INSFORGE_MEM − POSTGREST_MEM, so the only way to exceed usable is the small POSTGRES_LINEAR rounding floor (:81), which is negligible. Pre-existing structure; a hard guard/exit here would make the invariant self-checking, but not required.
  • Security — no security-relevant changes. No new user-controlled input reaches the shell/SQL; all -c values are fixed literals generated by the script. The app.encryption_key line is unchanged, and nothing secret is newly logged.

Verdict

approved — zero Critical findings. Two non-blocking Suggestions (raise/re-note effective_cache_size; account for hash_mem_multiplier in the follow-up tuning). The rebalance logic, tier thresholds (mirroring the existing connection-scaling block), and backward-compat defaults all check out. (Informational verdict — explicit GitHub approval remains a separate human action.)

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - approved.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="auto-scale-memory.sh">

<violation number="1" location="auto-scale-memory.sh:80">
P2: Near the minimum supported RAM sizes, rounding the linear values up makes the generated limits exceed `USABLE_MEM`, consuming part of the system reservation. Keep the remainder allocation when it is smaller than the rounded linear estimate.</violation>

<violation number="2" location="auto-scale-memory.sh:112">
P1: Concurrent complex queries can exceed the PostgreSQL cgroup limit and be OOM-killed at the configured connection limits. Size `PG_WORK_MEM` from a bounded concurrent-query budget (and cap `autovacuum_work_mem`) rather than assigning 16MB to every potentially active backend.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread auto-scale-memory.sh
# there's headroom to raise it, but that's a follow-up load test, not a guess.
# work_mem is per sort/hash node, so it's bounded per tier rather than scaled
# linearly (worst-case work_mem * max_connections must stay within the container).
if [ "$TOTAL_MEM" -ge 30000 ]; then PG_SHARED_BUFFERS=2GB; PG_EFFECTIVE_CACHE_SIZE=8GB; PG_WORK_MEM=16MB; PG_MAINTENANCE_WORK_MEM=1GB # 2xl ~32G

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Concurrent complex queries can exceed the PostgreSQL cgroup limit and be OOM-killed at the configured connection limits. Size PG_WORK_MEM from a bounded concurrent-query budget (and cap autovacuum_work_mem) rather than assigning 16MB to every potentially active backend.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At auto-scale-memory.sh, line 112:

<comment>Concurrent complex queries can exceed the PostgreSQL cgroup limit and be OOM-killed at the configured connection limits. Size `PG_WORK_MEM` from a bounded concurrent-query budget (and cap `autovacuum_work_mem`) rather than assigning 16MB to every potentially active backend.</comment>

<file context>
@@ -80,6 +100,25 @@ else                                  PG_MAX_CONNECTIONS=30;  PGRST_DB_POOL=15
+# there's headroom to raise it, but that's a follow-up load test, not a guess.
+# work_mem is per sort/hash node, so it's bounded per tier rather than scaled
+# linearly (worst-case work_mem * max_connections must stay within the container).
+if   [ "$TOTAL_MEM" -ge 30000 ]; then PG_SHARED_BUFFERS=2GB;   PG_EFFECTIVE_CACHE_SIZE=8GB;   PG_WORK_MEM=16MB; PG_MAINTENANCE_WORK_MEM=1GB    # 2xl ~32G
+elif [ "$TOTAL_MEM" -ge 15000 ]; then PG_SHARED_BUFFERS=1GB;   PG_EFFECTIVE_CACHE_SIZE=4GB;   PG_WORK_MEM=16MB; PG_MAINTENANCE_WORK_MEM=512MB  # xl ~16G
+elif [ "$TOTAL_MEM" -ge 7500  ]; then PG_SHARED_BUFFERS=512MB; PG_EFFECTIVE_CACHE_SIZE=2GB;   PG_WORK_MEM=8MB;  PG_MAINTENANCE_WORK_MEM=256MB  # large ~8G
</file context>

Comment thread auto-scale-memory.sh
Comment on lines +80 to +81
POSTGRES_MEM=$(( USABLE_MEM - INSFORGE_MEM - POSTGREST_MEM ))
if [ "$POSTGRES_MEM" -lt "$POSTGRES_LINEAR" ]; then POSTGRES_MEM=$POSTGRES_LINEAR; fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Near the minimum supported RAM sizes, rounding the linear values up makes the generated limits exceed USABLE_MEM, consuming part of the system reservation. Keep the remainder allocation when it is smaller than the rounded linear estimate.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At auto-scale-memory.sh, line 80:

<comment>Near the minimum supported RAM sizes, rounding the linear values up makes the generated limits exceed `USABLE_MEM`, consuming part of the system reservation. Keep the remainder allocation when it is smaller than the rounded linear estimate.</comment>

<file context>
@@ -55,10 +55,30 @@ fi
+# Postgres gets everything left after the (capped) app + proxy — it's the
+# workload that actually uses the memory. Never below its original linear share.
+POSTGRES_LINEAR=$(awk "BEGIN {printf \"%.0f\", $POSTGRES_BASE * $SCALE_FACTOR}")
+POSTGRES_MEM=$(( USABLE_MEM - INSFORGE_MEM - POSTGREST_MEM ))
+if [ "$POSTGRES_MEM" -lt "$POSTGRES_LINEAR" ]; then POSTGRES_MEM=$POSTGRES_LINEAR; fi
 # GHC heap cap for postgrest. Leave ~20MB for non-heap (binary, RTS internals,
</file context>
Suggested change
POSTGRES_MEM=$(( USABLE_MEM - INSFORGE_MEM - POSTGREST_MEM ))
if [ "$POSTGRES_MEM" -lt "$POSTGRES_LINEAR" ]; then POSTGRES_MEM=$POSTGRES_LINEAR; fi
POSTGRES_MEM=$(( USABLE_MEM - INSFORGE_MEM - POSTGREST_MEM ))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants